package nsip.scheduling;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import java.util.Set;
import nsip.model.Item;

/**
 *
 * @author Markus Goetz
 */
public abstract class AbstractSchedule {
    
    public static final int POSSIBLE_ORDER_PRODUCTION_DAYS = 3;
    public static final int DAYS_TO_BE_PLANNED = 12;
    public static final double SCHEDULE_FULL_PERCENTAGE = 0.95;

    private List<Day> days;
    private final int currentDate;
    protected double overplanRatio;
    protected Random random;

    private Set<Item> placedRfqs;
    
    /**
     *
     * @param overplanRatio amount of relative overbooking (e.g. 1.50) of available cycles 
     * @param currentDay
     * @param random
     */
    public AbstractSchedule(double overplanRatio, int currentDay, Random random) {
        
        if (overplanRatio > 2.0 || overplanRatio < 0.5) {
            throw new IllegalArgumentException(
                "Argument out of bounds: overplanRatio. Expected (0.5,2)");
        }

        this.days = createDays(currentDay, DAYS_TO_BE_PLANNED);
        this.currentDate = currentDay;
        this.random = random;
        this.overplanRatio = overplanRatio;
        this.placedRfqs = new HashSet<Item>();
    }

    private List<Day> createDays(int currentDay, int daysToBePlanned) {
        ArrayList<Day> result = new ArrayList(daysToBePlanned);
        
        for (int i = 0; i < daysToBePlanned; i++) {
            result.add(new Day(currentDay + i, Day.CYCLES_PER_DAY));
        }

        return result;
    }

    public double getAverageUtilization() {
        double utilizationSum = 0.0;
        for (Day day: days) {
            utilizationSum += day.getUtilization();
        }

        return (utilizationSum / days.size());
    }
    
    protected ListIterator<Day> getDaysIterator(int firstDay) {
        int index = firstDay - currentDate;
        if (index > days.size() - 1) {
            throw new IllegalArgumentException("Day#" + firstDay + " not in schedule");
        }
        return days.listIterator(index);
    }

    /**
     * @return the currentDay
     */
    public int getCurrentDate() {
        return currentDate;
    }

    public double getOverplanRatio() {
        return overplanRatio;
    }

    public Day getDay(int date) {
        return days.get(date - currentDate);
    }
    
    public void fillWith(List<Item> rfqs) {
        List<Item> unplacedRfqs = new ArrayList(rfqs);
        
        // Second place rfqs randomly
        while (!unplacedRfqs.isEmpty()) {
            Item rfq = unplacedRfqs.remove(random.nextInt(unplacedRfqs.size()));
            tryPlaceRfq(rfq);
        }
    }
    
    private void tryPlaceRfq(Item rfq) {
        List<Day> productionDays = getProductionDays(rfq, true, false);
        if (productionDays.isEmpty()) {
            return;
        }

        Day day = productionDays.get(random.nextInt(productionDays.size()));
        placeRfq(rfq, day);
    }

    protected List<Day> getProductionDays(Item order, boolean overplan, boolean produceLate) {
        int lastDay = Math.max(currentDate, order.getProductionDueDate());
        int firstDay = Math.max(currentDate, lastDay - POSSIBLE_ORDER_PRODUCTION_DAYS);

        List<Day> productionDays = new LinkedList();
        ListIterator<Day> iterator = getDaysIterator(firstDay);

        while (iterator.hasNext()) {
            Day day = iterator.next();
            int availableCycles = (int)
                (day.getAvailableCycles() * (overplan ? overplanRatio : 1.0));
           
            if (availableCycles >= order.getProductionCyclesNeeded()) {
                productionDays.add(day);
            }

            if (day.getDate() == lastDay && !(produceLate && productionDays.isEmpty())) {
                break;
            }
        }

        return productionDays;
    }

    protected boolean placeRfq(Item rfq, Day day) {
        if (day.getAvailableCycles() * overplanRatio >= rfq.getProductionCyclesNeeded()) {
            day.place(rfq);
            placedRfqs.add(rfq);
            return true;
        }

        return false;
    }

    /**
     * @return the placedRfqs
     */
    public Set<Item> getPlacedRfqs() {
        return placedRfqs;
    }

    @Override
    public String toString() {
        return super.toString();
    }
}
